let class2type = {};
let toString = class2type.toString; // 相当于：Object.prototype.toString
let getProto = Object.getPrototypeOf;
let hasOwn = class2type.hasOwnProperty;
let fnToString = hasOwn.toString;
let ObjectFunctionString = fnToString.call(Object);
let funProto = Function.prototype;

/**
 * 检测数据的类型
 *    1. 首先检测特殊人员null
 *    2. 其次善用typeof
 *        2.1 能使用typeof就使用
 *        2.2 不能使用的用 Object.prototype.toString.call()
 * @param {*} obj 检测对象
 * @returns
 */
export const toType = function toType(obj) {
  if (obj == null) {
    return obj + "";
  }
  return typeof obj === "object" || typeof obj === "function"
    ? class2type[toString.call(obj)] || "object"
    : typeof obj;
};

/**
 * 判断是否是函数， 如果typeof是函数，同时排除<object>元素的干扰
 * @param {*} obj 输入
 * @returns
 */
export const isFunction = function isFunction(obj) {
  // 通过typeof判断是否是函数，同时需要确认 <object>元素导致的例外
  // 因此需要确定是否是dom元素
  return typeof obj === "function" && typeof obj.nodeType !== "number";
};

/**
 * 判断是否是window对象， 如果是对象同时其中的window属性和自己相等
 * @param {*} obj 输入
 * @returns
 */
export const isWindow = function isWindow(obj) {
  return obj != null && obj.window === obj;
};

/**
 * 判断是否是类数组？这里是判断类数组还是数组啊，其实都在里面
 *    1. 如果是tostring是数组就是数组
 *    2. 如果length值为0
 *    3. 如果length是数字，且大于0，同时n-1也是一个属性
 * @param {*} obj 输入
 * @returns
 */
export const isArrayLike = function isArrayLike(obj) {
  let length = !!obj && "length" in obj && obj.length,
    type = toType(obj);
  if (isFunction(obj) || isWindow(obj)) {
    return false;
  }
  return (
    type === "array" ||
    length === 0 ||
    (typeof length === "number" && length > 0 && length - 1 in obj)
  );
};

/**
 * 类似于数组的forEach方法，分成类对象和对象两种方式来处理
 * @param {*} obj 循环读取对象中的内容
 * @param {*} cb 回调函数
 * @returns 返回对象
 */
export const each = function each(obj, cb) {
  let length,
    i = 0;
  if (isArrayLike(obj)) {
    length = obj.length;
    for (; i < length; i++) {
      if (cb.call(obj[i], i, obj[i]) === false) {
        break; // 为啥break
      }
    }
  } else {
    let keys = Object.keys(obj);
    typeof Symbol === "undefined"
      ? (keys = keys.concat(Object.getOwnPropertySymbols(obj)))
      : null;
    for (; i < keys.length; i++) {
      let key = keys[i];
      if (cb.call(obj[key], key, obj[key]) === false) {
        break;
      }
    }
  }
  return obj;
};

each(
  "Number String Boolean Function Array Date RegExp Object Error Symbol".split(
    " "
  ),
  (key, value) => {
    class2type["[Object " + value + "]"] = value.toLowerCase();
  }
);

/**
 * 检测是否是简单对象
 *    1. 判断是否是对象
 *    2. 判断是否有原型
 *    3. 判断constructor指向，最终的constructor要指向同一个对象
 * @param {*} obj
 * @returns
 */
export const isPlainObject = function isPlainObject(obj) {
  let proto, Ctor;
  if (!obj || toString.call(obj) !== "[object Object]") {
    return false;
  }
  proto = getProto(obj);
  if (!proto) return true;
  Ctor = hasOwn.call(proto, "constructor") && proto.constructor;

  return (
    typeof Ctor === "function" && fnToString.call(Ctor) === ObjectFunctionString
  );
};

/**
 * 浅合并
 * 合并规律（A,B）
 *  - A/B都是对象: 迭代B，依次更新A
 *  - A不是对象，B是对象： B替换A
 *  - A是对象，B不是对象： 依然以A的值为主
 *  - A/B都不是对象： B替换A
 * @param {*} obj1
 * @param {*} obj2
 * @returns
 */
export const shallowMerge = function shallowMerge(obj1, obj2) {
  let isPlain1 = isPlainObject(obj1),
    isPlain2 = isPlainObject(obj2);
  if (!isPlain1) return obj2;
  if (!isPlain2) return obj1;
  each(obj2, (key, value) => {
    obj1[key] = value;
  });

  return obj1;
};

/**
 * 深合并
 * @param {*} obj1
 * @param {*} obj2
 * @param {*} cache
 * @returns
 */
export const deepMerge = function deepMerge(obj1, obj2, cache) {
  cache = Array.isArray(cache) ? cache : [];
  if (cache.indexOf(obj2) >= 0) return obj2;
  cache.push(obj2);
  let isPlain1 = isPlainObject(obj1),
    isPlain2 = isPlainObject(obj2);
  if (!isPlain1 || !isPlain2) return shallowMerge(obj1, obj2);
  each(obj2, (key, value) => {
    obj1[key] = deepMerge(obj1[key], value, cache);
  });
  return obj1;
};
